package monitor

import (
	"encoding/json"
	"fmt"
	"gluenet/pkg/apis"
	"gluenet/pkg/meta"
	"gluenet/pkg/platform/glue"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"net/http"
	"reflect"
)

const (
	testUrldefault = `http://152.136.134.100:30025`
	//testUrldefault = `http://152.136.134.100:30030`

	getService = `%v/api/services`

	// getTrace param url, limit, service, start, end, guid
	getTraces      = `%v/api/traces?limit=%v&service=%v&start=%v&end=%v&tags=%%7B%%22hostname%%22%%3A%%22%v%%22%%7D`
	getTraceFromId = `http://152.136.134.100:30025/api/traces?traceID=11298f48643a06e6`
)

func GetTraces(c *apis.GlueContext) {
	guid := c.WebContext.URLParam("guid")
	//platform := c.WebContext.URLParam("platform")
	traceId := c.WebContext.URLParam("traceId")
	startTime, _ := c.WebContext.URLParamInt64("start")
	endTime, _ := c.WebContext.URLParamInt64("end")
	limit, _ := c.WebContext.URLParamInt64("everyServerLimit")
	//guid = "test-helm-social-network"

	var traces = make(map[string]interface{})
	if traceId != "" {
		if err := c.DataBase.GetTracesId(traceId, traces); err != nil {
			c.Logger.Errorf("query id error :%v", err)
			c.WebContext.JSON(apis.ResponseReqErr(err, nil))
			return
		}
	} else {
		var ins *meta.Instance
		if err := c.DataBase.InstanceGet(guid, &ins); err != nil {
			c.Logger.Errorf("query instance error :%v", err)
			c.WebContext.JSON(apis.ResponseReqErr(err, nil))
			return
		}
		//var ins []meta.Instance
		//if err := orm.InstanceGet(c.DataBase, guid, &ins); err != nil {
		//	c.Logger.Errorf("query instance error :%v", err)
		//	c.WebContext.JSON(apis.ResponseReqErr(err, nil))
		//	return
		//}
		//if len(ins) == 0 {
		//	c.Logger.Errorf("instance not exist")
		//	c.WebContext.JSON(apis.ResponseReqErr(fmt.Errorf("instance not exist"), nil))
		//	return
		//}

		switch ins.Platform {
		case apis.PlatfromGluenets:
			var gins glue.Instance
			if err := json.Unmarshal([]byte(ins.InstanceValue), &gins); err != nil {
				c.Logger.Errorf("unmarshall error :%v", err)
				c.WebContext.JSON(apis.ResponseReqErr(err, nil))
				return
			}
			var containers glue.Containers
			if err := yaml.Unmarshal([]byte(gins.InstanceYaml), &containers); err != nil {
				c.Logger.Errorf("unmarshall error :%v", err)
				c.WebContext.JSON(apis.ResponseReqErr(err, nil))
				return
			}
			var cts, i = make([]string, len(containers.Containers)), 0
			for k := range containers.Containers {
				cts[i] = k
				i++
			}

			if err := c.DataBase.GetTracesGluenets(guid, cts, startTime*1000000, endTime*1000000, limit, traces); err != nil {
				c.Logger.Errorf("query error :%v", err)
				c.WebContext.JSON(apis.ResponseReqErr(err, nil))
				return
			}
		case apis.PlatfromKubernetes:
			if err := c.DataBase.GetTracesK8s(guid, startTime*1000000, endTime*1000000, limit, traces); err != nil {
				c.Logger.Errorf("query error :%v", err)
				c.WebContext.JSON(apis.ResponseReqErr(err, nil))
				return
			}
		}
	}

	//k, _ := json.Marshal(traces)
	c.WebContext.JSON(apis.ResponseOK(traces))
}

func GetDag(c *apis.GlueContext) {
	guid := c.WebContext.URLParam("guid")
	startTime := c.WebContext.URLParam("start")
	endTime := c.WebContext.URLParam("end")
	limit := c.WebContext.URLParam("everyServerLimit")

	url := testUrldefault
	guid = "test.app.guid"
	startTime = "1650244161081000"
	endTime = "1650416961081000"
	limit = "20"

	traces, err := jaegerTraces(url, guid, limit, startTime, endTime)
	if err != nil {
		fmt.Printf("error :%v", err)
		return
	}

	bts, err := dag(traces)
	if err != nil {
		c.Logger.Errorf("query error :%v", err)
		c.WebContext.JSON(apis.ResponseReqErr(err, nil))
		return
	}
	c.WebContext.Write(bts)
}

func dag(traces []interface{}) ([]byte, error) {
	type Ikey struct {
		Hostname string
		Ip       string
	}

	type Pkey struct {
		ServiceName string
		Node        Ikey
		Uuid        string
	}

	type PValue struct {
		Key   Pkey
		PName string
	}

	type Skey struct {
		TraceId string
		SpanId  string
	}

	type SValue struct {
		OperateName string
		Key         Skey
		ProcessKey  Pkey
	}

	type Slink struct {
		Source Skey
		Target Skey
		Type   string
	}

	instanceMap, instancelist, nlid := make(map[Ikey]int), []Ikey{}, 0
	processMap, processlist, plid := make(map[Pkey]int), []PValue{}, 0
	spanMap, spanlist, slid := make(map[Skey]int), []SValue{}, 0
	spanlinksMap, spanlinkslist, sllid := make(map[Slink]int), []Slink{}, 0
	for _, v := range traces {
		p, err := getProcesses(v)
		if err != nil {
			return nil, err
		}
		for pn, pv := range p {
			nk := Ikey{Hostname: pv[1], Ip: pv[2]}
			if _, ok := instanceMap[nk]; !ok {
				instanceMap[nk] = nlid
				instancelist = append(instancelist, nk)
				nlid++
			}
			pk := Pkey{ServiceName: pv[0], Node: nk, Uuid: pv[3]}
			pkV := PValue{Key: pk, PName: pn}
			if _, ok := processMap[pk]; !ok {
				processMap[pk] = plid
				processlist = append(processlist, pkV)
				plid++
			}
		}

		o, err := getOperate(v)
		if err != nil {
			return nil, err
		}

		for _, ov := range o {
			ona := ov[0].(string)
			tid := ov[1].(string)
			sid := ov[2].(string)
			pn := ov[3].(string)
			pid := Pkey{
				ServiceName: p[pn][0],
				Node: Ikey{
					Hostname: p[pn][1],
					Ip:       p[pn][2],
				},
				Uuid: p[pn][3],
			}
			lks := ov[4].([]interface{})

			ok := Skey{TraceId: tid, SpanId: sid}
			oV := SValue{OperateName: ona, Key: ok, ProcessKey: pid}
			if _, exist := spanMap[ok]; !exist {
				spanMap[ok] = slid
				spanlist = append(spanlist, oV)
				slid++
			}

			for _, lv := range lks {
				ref := lv.(map[string]interface{})
				lk := Slink{
					Target: ok,
					Source: Skey{
						TraceId: ref["traceID"].(string),
						SpanId:  ref["spanID"].(string),
					},
					Type: ref["refType"].(string),
				}
				if _, exist := spanlinksMap[lk]; !exist {
					spanlinksMap[lk] = sllid
					spanlinkslist = append(spanlinkslist, lk)
					sllid++
				}
			}
		}
	}

	type OKey struct {
		OperateName string
		ProcessKey  Pkey
	}

	type Olink struct {
		Source OKey
		Target OKey
	}

	operateMap, operatelist, olid := make(map[OKey]int), []OKey{}, 0

	for _, v := range spanlist {
		ok := OKey{
			ProcessKey:  v.ProcessKey,
			OperateName: v.OperateName,
		}
		if _, exist := operateMap[ok]; !exist {
			operateMap[ok] = olid
			operatelist = append(operatelist, ok)
			olid++
		}
	}

	operatelinksMap, operatelinkslist, ollid := make(map[Olink]int), []Olink{}, 0
	for _, v := range spanlinkslist {
		olk := Olink{
			Source: OKey{
				ProcessKey:  spanlist[spanMap[v.Source]].ProcessKey,
				OperateName: spanlist[spanMap[v.Source]].OperateName,
			},
			Target: OKey{
				ProcessKey:  spanlist[spanMap[v.Target]].ProcessKey,
				OperateName: spanlist[spanMap[v.Target]].OperateName,
			},
		}
		if _, exist := operatelinksMap[olk]; !exist {
			operatelinksMap[olk] = ollid
			operatelinkslist = append(operatelinkslist, olk)
			ollid++
		}
	}

	type Plink struct {
		Source Pkey
		Target Pkey
	}

	processlinkMap, processlinklist, pllid := make(map[Plink]int), []Plink{}, 0
	for _, v := range operatelinkslist {
		plk := Plink{
			Source: v.Source.ProcessKey,
			Target: v.Target.ProcessKey,
		}
		if _, exist := processlinkMap[plk]; !exist {
			processlinkMap[plk] = pllid
			processlinklist = append(processlinklist, plk)
			pllid++
		}
	}

	type Ilink struct {
		Source Ikey
		Target Ikey
	}

	instancelinkMap, instancelinklist, illid := make(map[Ilink]int), []Ilink{}, 0
	for _, v := range processlinklist {
		ilk := Ilink{
			Source: v.Source.Node,
			Target: v.Target.Node,
		}
		if _, exist := instancelinkMap[ilk]; !exist {
			instancelinkMap[ilk] = illid
			instancelinklist = append(instancelinklist, ilk)
			illid++
		}
	}

	type link struct {
		Source int
		Target int
	}
	newIlink, newPlink, newOlink := make([]link, len(instancelinklist)), make([]link, len(processlinklist)), make([]link, len(operatelinkslist))
	for k, v := range instancelinklist {
		newIlink[k] = link{
			Source: instanceMap[v.Source],
			Target: instanceMap[v.Target],
		}
	}

	for k, v := range processlinklist {
		newPlink[k] = link{
			Source: processMap[v.Source],
			Target: processMap[v.Target],
		}
	}

	for k, v := range operatelinkslist {
		newOlink[k] = link{
			Source: operateMap[v.Source],
			Target: operateMap[v.Target],
		}
	}

	type newP struct {
		InstanceIndex int
		PorcessesName string
		ServiceName   string
		Uuid          string
	}

	type newO struct {
		ProcessesIndex int
		OperateName    string
		ServiceName    string
	}

	newPlist, newOlist := make([]newP, len(processlist)), make([]newO, len(operatelist))
	for k, v := range processlist {
		p := newP{
			InstanceIndex: instanceMap[v.Key.Node],
			PorcessesName: v.PName,
			ServiceName:   v.Key.ServiceName,
			Uuid:          v.Key.Uuid,
		}
		newPlist[k] = p
	}
	for k, v := range operatelist {
		o := newO{
			ProcessesIndex: processMap[v.ProcessKey],
			OperateName:    v.OperateName,
			ServiceName:    v.ProcessKey.ServiceName,
		}
		newOlist[k] = o
	}

	res := map[string]interface{}{
		"instances":     instancelist,
		"instanceLinks": newIlink,
		//"instanceLinksDebug": instancelinklist,
		//"processes":      processlist,
		"processes":      newPlist,
		"processesLinks": newPlink,
		//"processesLinkDebug": processlinklist,
		//"operates":     operatelist,
		"operates":     newOlist,
		"operateLinks": newOlink,
		//"operateLinksDebug":  operatelinkslist,
	}

	rb, _ := json.Marshal(res)

	return rb, nil
}

func getOperate(trace interface{}) ([][5]interface{}, error) {
	t1, ok := trace.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("trace is mot map[string]interface %v", reflect.TypeOf(trace))
	}

	t2, ok := t1["spans"].([]interface{})
	if !ok {
		return nil, fmt.Errorf("spans is mot map[string]interface%v", reflect.TypeOf(t1["spans"]))
	}

	res, i := make([][5]interface{}, len(t2)), 0
	for _, v := range t2 {
		span := v.(map[string]interface{})
		if span["warnings"] != nil {
			continue
		}
		res[i][0] = span["operationName"]
		res[i][1] = span["traceID"]
		res[i][2] = span["spanID"]
		res[i][3] = span["processID"]
		res[i][4] = span["references"]
		i++
	}

	return res[:i], nil
}

func getProcesses(trace interface{}) (map[string][4]string, error) {
	t1, ok := trace.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("trace is mot map[string]interface %v", reflect.TypeOf(trace))
	}

	t2, ok := t1["processes"].(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("processes is mot map[string]interface%v", reflect.TypeOf(t1["processes"]))
	}

	res := make(map[string][4]string)
	for p, v := range t2 {
		pv, ok := v.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("processes %v is mot map[string]interface", p)
		}
		serviceName, hostname, ip, uuid := pv["serviceName"].(string), "", "", ""
		tags := pv["tags"].([]interface{})
		for _, tv := range tags {
			tag, ok := tv.(map[string]interface{})
			if !ok {
				return nil, fmt.Errorf("tag %v is mot map[string]interface", tv)
			}
			switch tag["key"].(string) {
			case "client-uuid":
				uuid = tag["value"].(string)
			case "hostname":
				hostname = tag["value"].(string)
			case "ip":
				ip = tag["value"].(string)
			}
		}

		va := [4]string{serviceName, hostname, ip, uuid}
		res[p] = va
	}
	return res, nil
}

func jaegerTraces(url, guid, limit, start, end string) ([]interface{}, error) {
	r1, err := http.Get(`http://152.136.134.100:30030/api/services`)
	//r1, err := http.Get(fmt.Sprintf(getService, url))
	if err != nil {
		return nil, err
	}
	bts, _ := ioutil.ReadAll(r1.Body)
	servics := make(map[string]interface{})
	json.Unmarshal(bts, &servics)
	servicsName := make([]string, len(servics["data"].([]interface{})))
	for k, v := range servics["data"].([]interface{}) {
		servicsName[k] = v.(string)
	}

	var traces = []interface{}{}
	for _, v := range servicsName {
		//r2, err := http.Get(fmt.Sprintf(getTraces, url, limit, v, start, end, guid))
		r2, err := http.Get(fmt.Sprintf(`http://152.136.134.100:30030/api/traces?end=1650422553318000&limit=20&lookback=1h&maxDuration&minDuration&service=%v&start=1650418953318000`, v))
		if err != nil {
			return nil, err
		}
		bts, _ := ioutil.ReadAll(r2.Body)
		ts := make(map[string]interface{})
		json.Unmarshal(bts, &ts)

		traces = append(traces, ts["data"].([]interface{})...)
	}

	return traces, nil
}
